﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QualityJudgingApp
{
    class AnalysisLogic
    {
        List<RawDataEntry> myLogs = null;
        //double slopeTime = 0;
        //double interTime = 0;
        //double slopeEdit = 0;
        //double interEdit = 0;
        double[] coefficientsTime;
        double[] coefficientsEdit;

        public double CurrentPosRatio = 0;
        public double CurrentNegRatio = 0;
        public double precision = 0;
        public double recall = 0;
        public double fvalue = 0;
        public double fvalue2 = 0;
        public double accuracy = 0;
        public double optimalX = 0;
        LinearRegression myRegression;

        public AnalysisLogic(List<RawDataEntry> Logs)
        {
            myLogs = Logs;
            myRegression = new LinearRegression();
            coefficientsTime = new double[3];
            coefficientsEdit = new double[3];
        }

        public List<int> getAllUserIDs()
        {
            List<int> userIds = new List<int>();
            int lastID = -1;
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                if (myLogs[i].UserID != lastID)
                {
                    lastID = myLogs[i].UserID;
                    userIds.Add(lastID);
                }
            }
            return userIds;
        }
        private int FindIndex(List<string> ids, string id)
        {
            for (int i = 0; i < ids.Count; i++)
            {
                if (ids[i].Equals(id))//found!
                    return i;
            }
            return -1;
        }

        //public int[] getWorkersDistribution()
        //{
        //    List<string> workersID = new List<string>();
        //    for (int i = 0; i < myLogs.Count; i += 1)
        //    {
        //        if (!workersID.Contains(myLogs[i].Worker_ID))
        //        {
        //            workersID.Add(myLogs[i].Worker_ID);
        //        }
        //    }
        //    int[] workersFreqs = new int[workersID.Count];
        //    for (int i = 0; i < myLogs.Count; i += 1)
        //    {
        //        int index = FindIndex(workersID, myLogs[i].Worker_ID);
        //        workersFreqs[index]++;
        //    }
        //    return workersFreqs;
        //}

        //public string GetParameters(List<int> chosenIDs, List<int> chosenInternalIDs, int condition)//1: use non-negative only; 2: use all
        public string GetParameters(List<int> chosenIDs, List<int> chosenInternalIDs)//, int condition)//1: use non-negative only; 2: use all
        {
            List<double> audioLengths = new List<double>();
            List<double> audioQuailties = new List<double>();
            List<double> pictureQuailties = new List<double>();

            List<double> workerTimes = new List<double>();
            List<double> workerEdits = new List<double>();

            for (int i = 0; i < myLogs.Count; i += 1)
            {
                RawDataEntry tempLog = myLogs[i];
                if (chosenInternalIDs.Contains(i%5)&&chosenIDs.Contains(tempLog.UserID))
                {
                    //if (condition == 0 && tempLog.Quality < 0)
                    //    continue;
                    //else
                    {
                        audioLengths.Add(tempLog.audioLength);
                        audioQuailties.Add(tempLog.audioQuality);
                        pictureQuailties.Add(tempLog.pictureQuality);

                        workerTimes.Add(tempLog.timeSpent);
                        workerEdits.Add(tempLog.answerLength);
                    }
                }
            }

            string output = "Total counts: " + audioLengths.Count + "\n";
            coefficientsTime = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerTimes);
            coefficientsEdit = GetCoefficients(audioLengths, audioQuailties, pictureQuailties, workerEdits);

            //slopeTime = GetSlope(audioLengths, textLength);
            //interTime = GetIntercept(audioLengths, workerTimes, slopeTime);
            output += "Time=" + coefficientsTime[0] + "*AudioLength+" + coefficientsTime[1] + "*audioQuality+" + coefficientsTime[2] + "*pictureQuality+" + coefficientsTime[3] + "\n";
            output += "Edit=" + coefficientsEdit[0] + "*AudioLength+" + coefficientsEdit[1] + "*audioQuality+" + coefficientsEdit[2] + "*pictureQuality+" + coefficientsEdit[3] + "\n";
            return output;
        }

        public double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[2, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        public double[] GetCoefficients(List<double> xArray1, List<double> xArray2, List<double> xArray3, List<double> yArray)
        {
            double[] ys = new double[yArray.Count];
            double[,] xs = new double[3, yArray.Count];
            for (int i = 0; i < yArray.Count; i++)
            {
                ys[i] = yArray[i];
                xs[0, i] = xArray1[i];
                xs[1, i] = xArray2[i];
                xs[2, i] = xArray3[i];
            }
            myRegression.Regress(ys, xs);
            return myRegression.C;
        }

        //private double GetSlope(List<double> xArray, List<double> yArray)
        //{
        //    if (xArray == null)
        //        throw new ArgumentNullException("xArray");
        //    if (yArray == null)
        //        throw new ArgumentNullException("yArray");
        //    if (xArray.Count != yArray.Count)
        //        throw new ArgumentException("Array Length Mismatch");
        //    if (xArray.Count < 2)
        //        throw new ArgumentException("Arrays too short.");

        //    double n = xArray.Count;
        //    double sumxy = 0, sumx = 0, sumy = 0, sumx2 = 0;
        //    for (int i = 0; i < xArray.Count; i++)
        //    {
        //        sumxy += xArray[i] * yArray[i];
        //        sumx += xArray[i];
        //        sumy += yArray[i];
        //        sumx2 += xArray[i] * xArray[i];
        //    }
        //    return ((sumxy - sumx * sumy / n) / (sumx2 - sumx * sumx / n));
        //}

        //private double GetIntercept(List<double> xArray, List<double> yArray, double slope)
        //{
        //    double sumx = 0, sumy = 0;
        //    for (int i = 0; i < xArray.Count; i++)
        //    {
        //        sumx += xArray[i];
        //        sumy += yArray[i];
        //    }
        //    sumx /= xArray.Count;
        //    sumy /= yArray.Count;
        //    return sumy - sumx * slope;
        //}


        public double GetAccumulatePercentage(double intervalX, int quality, int type, List<int> chosenTrainingIDs, List<int> chosenInternalIds)//1: posotive; 0: neutral
        {
            //the following parameters are trained from 1-4 data
            //double timea = slopeTime;
            //double timeb = interTime;
            //double edita = slopeEdit;
            //double editb = interEdit;

            //the following are from 1-4 training also.
            int N = chosenInternalIds.Count+1;
            double weightIni = 1.0 / (N+1);
            //double weightIni = 1 / 6;
            double[] weights = new double[N];
            for (int i = 0; i < weights.Length; i++)
                weights[i] = 1.0 / ((N + 1) + i * intervalX);

            int[] recognitionCountsFirstFour = new int[9];
            //now evaluate:
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                RawDataEntry tempLog = myLogs[i];
                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                //coefficientsTime, coefficientsEdit
                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];
                double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * audioQuality + coefficientsEdit[2] * picutureQuality + coefficientsEdit[3];

                double bestGuessTime = initialTime * weightIni;
                double betsGuessEdits = initialEdits * weightIni;
                List<double> times = new List<double>();
                List<double> edits = new List<double>();
                for (int j = 0; j < 5; j++)
                {
                    if (chosenInternalIds.Contains(j))//only if chosen
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        edits.Add(myLogs[i + j].answerLength);
                    }
                }
                times.Sort();
                edits.Sort();
                for (int j = times.Count - 1; j >= 0; j--)
                {
                    bestGuessTime += times[j] * weights[j];
                    betsGuessEdits += edits[j] * weights[j];
                }

                int ThresTimes = (int)bestGuessTime;
                int ThresEdits = (int)betsGuessEdits;

                //double ThresTimesGoods = initialTime * weightIni;
                //double ThresEditGoods = initialEdits * weightIni;
                //if (quality == 0)
                //{
                //    double[] weightsGood = new double[5];
                //    for (int m = 0; m < weights.Length; m++)
                //        weightsGood[m] = 1.0 / (6 + m * intervalGood);
                //    for (int m = times.Count - 1; m >= 0; m--)
                //    {
                //        ThresTimesGoods += times[m] * weightsGood[m];
                //        ThresEditGoods += edits[m] * weightsGood[m];
                //    }
                //}
                //int ThresTimesGood = (int)ThresTimesGoods;
                //int ThresEditGood = (int)ThresEditGoods;
                //int[] recognitionCountsFirstFour = new int[9];

                if (chosenTrainingIDs.Contains(tempLog.UserID))
                {
                    for (int j = 0; j < 5; j++)
                    {
                        if (chosenInternalIds.Contains(j))
                        {
                            if (myLogs[i + j].quality >= 0)//positive as marked
                            {
                                //if ((type==1&&myLogs[i + j].TimeSpentTask >= ThresTimes) 
                                //    || (type==2&&myLogs[i + j].Proof_change_count >= ThresEdits))

                                if (myLogs[i + j].timeSpent >= ThresTimes && myLogs[i + j].answerLength >= ThresEdits)
                                {
                                    recognitionCountsFirstFour[0]++;
                                }
                                else
                                    recognitionCountsFirstFour[1]++;
                            }
                            else
                            {
                                if (myLogs[i + j].timeSpent >= ThresTimes && myLogs[i + j].answerLength >= ThresEdits)
                                {
                                    recognitionCountsFirstFour[2]++;
                                }
                                else
                                    recognitionCountsFirstFour[3]++;
                            }
                        }
                    }
                }
                else
                    continue;

            }
            double precision1 = 0;
            double recall1 = 0;

            if (quality == 1)
            {
                precision1 = (double)recognitionCountsFirstFour[3] / (recognitionCountsFirstFour[1] + recognitionCountsFirstFour[3]);
                recall1 = (double)recognitionCountsFirstFour[3] / (recognitionCountsFirstFour[2] + recognitionCountsFirstFour[3]);
            }
            return precision1 * recall1 / (precision1 + recall1) * 2;
        }

        public double FindBestInterval(double start, double end, int quality, double iteration, int type, List<int> chosenTrainingIds, List<int> chosenInternalIds)
        {
            double interval = 0.1;
            double bestRate = 0;
            double bestInterval = 0;
            double doubleInterval = start;
            for (int i = 0; i < end*3; i++)
            {
                double accuStart = GetAccumulatePercentage(doubleInterval, quality, type, chosenTrainingIds, chosenInternalIds);
                

                if (accuStart > bestRate)
                {
                    bestRate = accuStart;
                    bestInterval = doubleInterval;
                }
                doubleInterval += interval;
            }
            System.Diagnostics.Debug.WriteLine("Best interval: " + bestInterval + "\t rate: " + bestRate);
            return bestInterval;
            //if (iteration == 20)//10times, stop now
            //    return (start + end) / 2;
            //else
            //{
            //    double accuStart = GetAccumulatePercentage(start, quality, type, chosenTrainingIds, chosenInternalIds);
            //    double accuend = GetAccumulatePercentage(end, quality, type, chosenTrainingIds, chosenInternalIds);
            //    //System.Diagnostics.Debug.WriteLine("Iteration "+iteration+": S("+accuStart+"),  E("+accuend+")");
            //    if (accuStart > accuend)
            //    {
            //        return FindBestInterval(start, (start + end) / 2, quality, iteration + 1, type, chosenTrainingIds, chosenInternalIds);
            //    }
            //    else
            //    {
            //        return FindBestInterval((start + end) / 2, end, quality, iteration + 1, type, chosenTrainingIds, chosenInternalIds);
            //    }
            //}
            //return 0;
        }

        //backup Jan-04-2012
        public double FindBestInterval2(double start, double end, int quality, double iteration, int type, List<int> chosenTrainingIds, List<int> chosenInternalIds)
        {
            if (iteration == 20)//10times, stop now
                return (start + end) / 2;
            else
            {
                double accuStart = GetAccumulatePercentage(start, quality, type, chosenTrainingIds, chosenInternalIds);
                double accuend = GetAccumulatePercentage(end, quality, type, chosenTrainingIds, chosenInternalIds);
                //System.Diagnostics.Debug.WriteLine("Iteration "+iteration+": S("+accuStart+"),  E("+accuend+")");
                if (accuStart > accuend)
                {
                    return FindBestInterval(start, (start + end) / 2, quality, iteration + 1, type, chosenTrainingIds, chosenInternalIds);
                }
                else
                {
                    return FindBestInterval((start + end) / 2, end, quality, iteration + 1, type, chosenTrainingIds, chosenInternalIds);
                }
            }
            //return 0;
        }

        //public string NewAnalyzeSelectProfiles(List<int> chosenIds, List<int> chosenInternalIds, int adjustCondition)
        public void NewAnalyzeSelectProfiles(List<int> chosenIds, List<int> chosenInternalIds)
        {
            //the following parameters are trained from data
            //double timea = slopeTime;
            //double timeb = interTime;
            //double edita = slopeEdit;
            //double editb = interEdit;

            //the following are from 1-4 training also.
            double weightIni = 1.0 / (chosenInternalIds.Count+1);
            double intervalXGoodEffort = FindBestInterval(0, 20, 1, 0, 1, chosenIds, chosenInternalIds);
            double intervalXGoodCont = FindBestInterval(0, 20, 1, 0, 2, chosenIds, chosenInternalIds);
            //System.Diagnostics.Debug.WriteLine("Choosing Interval X for good as " + intervalXGood + "\n\n");
            //double intervalXneutral = FindBestInterval(intervalXGood, 6, 0, 0, intervalXGood);
            //System.Diagnostics.Debug.WriteLine("Choosing Interval X for neutral as " + intervalXneutral + "\n\n");

            optimalX = (intervalXGoodEffort + intervalXGoodCont) / 2;

            double[] weightsGoodEffort = new double[chosenInternalIds.Count];
            for (int i = 0; i < weightsGoodEffort.Length; i++)
                weightsGoodEffort[i] = 1.0 / (chosenInternalIds.Count+1 + i * intervalXGoodEffort);

            double[] weightsGoodCont = new double[chosenInternalIds.Count];
            for (int i = 0; i < weightsGoodCont.Length; i++)
                weightsGoodCont[i] = 1.0 / (chosenInternalIds.Count + 1 + i * intervalXGoodCont);

            //double[] weightsNeutral = new double[5];
            //for (int i = 0; i < weightsNeutral.Length; i++)
            //    weightsNeutral[i] = 1.0 / (6 + i * intervalXneutral);

            int[] recognitionCounts = new int[9];
            int[] recognitionCountsFirstFour = new int[9];
            //now evaluate:
            for (int i = 0; i < myLogs.Count; i += 5)
            {
                //System.Diagnostics.Debug.WriteLine("Processing " + (i / 5 + 1) + "/" + myLogs.Count / 5 + "...");
                RawDataEntry tempLog = myLogs[i];
                double audioLength = tempLog.audioLength;
                double audioQuality = tempLog.audioQuality;
                double picutureQuality = tempLog.pictureQuality;

                double initialTime = coefficientsTime[0] * audioLength + coefficientsTime[1] * audioQuality + coefficientsTime[2] * picutureQuality + coefficientsTime[3];
                double initialEdits = coefficientsEdit[0] * audioLength + coefficientsEdit[1] * audioQuality + coefficientsEdit[2] * picutureQuality + coefficientsEdit[3];

                double bestGuessTimeGood = initialTime * weightIni;
                double bestGuessEditsGood = initialEdits * weightIni;

                //double bestGuessTimeNeutral = initialTime * weightIni;
                //double bestGuessEditsNeutral = initialEdits * weightIni;

                List<double> times = new List<double>();
                List<double> edits = new List<double>();

                double timesTotal = 0;
                double editTotal = 0;
                for (int j = 0; j < 5; j++)
                {
                    if(chosenInternalIds.Contains(j))
                    {
                        times.Add(myLogs[i + j].timeSpent);
                        edits.Add(myLogs[i + j].answerLength);
                        timesTotal += myLogs[i + j].timeSpent;
                        editTotal += myLogs[i + j].answerLength;
                    }
                }

                times.Sort();
                edits.Sort();
                //adjust the initial estimation if beyond the range from users
                //simply choose 1st one as this is the most reliable
                //if (adjustCondition == 3)//use closest point to replace
                //{
                //    if (bestGuessTimeGood > times[0] * weightIni)//outliers
                //    {
                //        bestGuessTimeGood = times[0] * weightIni;
                //    }
                //    else if (bestGuessTimeGood < times[times.Count - 1] * weightIni)
                //    {
                //        bestGuessTimeGood = times[times.Count-1] * weightIni;
                //    }

                //    if (bestGuessEditsGood > edits[0] * weightIni)//outliers
                //    {
                //        bestGuessEditsGood = edits[0] * weightIni;
                //    }
                //    else if (bestGuessEditsGood < edits[edits.Count - 1] * weightIni)
                //    {
                //        bestGuessEditsGood = edits[edits.Count - 1] * weightIni;
                //    }
                //}
                //else if (adjustCondition == 2)//use average
                //{
                //    if (bestGuessTimeGood > times[0] * weightIni || bestGuessTimeGood < times[times.Count - 1] * weightIni)//outliers
                //    {
                //        bestGuessTimeGood = timesTotal / times.Count * weightIni;
                //    }

                //    if (bestGuessEditsGood > edits[0] * weightIni || bestGuessEditsGood < edits[edits.Count - 1] * weightIni)//outliers
                //    {
                //        bestGuessEditsGood = editTotal / edits.Count * weightIni;
                //    }
                //}
                //else if (adjustCondition == 1)//use extreme
                //{
                //    if (bestGuessTimeGood > times[0] * weightIni || bestGuessTimeGood < times[times.Count - 1] * weightIni)//outliers
                //    {
                //        bestGuessTimeGood = times[0] * weightIni;
                //    }

                //    if (bestGuessEditsGood > edits[0] * weightIni || bestGuessEditsGood < edits[edits.Count - 1] * weightIni)//outliers
                //    {
                //        bestGuessEditsGood = edits[0] * weightIni;
                //    }
                //}

                for (int j = times.Count - 1; j >= 0; j--)
                {
                    bestGuessTimeGood += times[j] * weightsGoodEffort[j];
                    bestGuessEditsGood += edits[j] * weightsGoodCont[j];

                    //bestGuessTimeNeutral += times[j] * weightsNeutral[j];
                    //bestGuessEditsNeutral += edits[j] * weightsNeutral[j];
                }

                int ThresTimesGood = (int)bestGuessTimeGood;
                int ThresEditsGood = (int)bestGuessEditsGood;

                //int ThresTimesNeutral = (int)bestGuessTimeNeutral;
                //int ThresEditsNeutral = (int)bestGuessEditsNeutral;

                if (chosenIds.Contains(tempLog.UserID))
                {
                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality >= 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= ThresTimesGood && myLogs[i + j].answerLength >= ThresEditsGood)
                            {
                                recognitionCountsFirstFour[0]++;
                            }
                            else
                                recognitionCountsFirstFour[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= ThresTimesGood && myLogs[i + j].answerLength >= ThresEditsGood)
                            {
                                recognitionCountsFirstFour[2]++;
                            }
                            else
                                recognitionCountsFirstFour[3]++;
                        }
                    }
                }
                else
                {
                    for (int j = 0; j < 5; j++)
                    {
                        if (myLogs[i + j].quality >= 0)//positive
                        {
                            if (myLogs[i + j].timeSpent >= ThresTimesGood && myLogs[i + j].answerLength >= ThresEditsGood)
                            {
                                recognitionCounts[0]++;
                            }
                            else
                                recognitionCounts[1]++;
                        }
                        else//negative
                        {
                            if (myLogs[i + j].timeSpent >= ThresTimesGood && myLogs[i + j].answerLength >= ThresEditsGood)
                            {
                                recognitionCounts[2]++;
                            }
                            else
                                recognitionCounts[3]++;
                        }
                    }

                }

                //System.Diagnostics.Debug.Write("\n" + bestGuessTime+"\t");
                //for (int j = 0; j < 5; j++)
                //    System.Diagnostics.Debug.Write("" + myLogs[i + j].TimeSpentTask+"("+myLogs[i + j].Quality+")");
                //System.Diagnostics.Debug.Write("\n" + betsGuessEdits + "\t");
                //for (int j = 0; j < 5; j++)
                //    System.Diagnostics.Debug.Write("" + myLogs[i + j].Proof_change_count + "(" + myLogs[i + j].Quality + ")");

            }
            string output = "";

            //for (int i = 0; i < 2; i++)
            //{
            //    for (int j = 0; j < 2; j++)
            //    {
            //        output += "" + recognitionCountsFirstFour[i * 2 + j] + "\t";
            //    }
            //}
            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    output += "" + recognitionCounts[i * 2 + j] + "\t";
                }
            }

            double PosRegRatio = (double)recognitionCounts[0] / (recognitionCounts[0] + recognitionCounts[1]);
            double NegRegRatio = (double)recognitionCounts[3] / (recognitionCounts[2] + recognitionCounts[3]);

            CurrentPosRatio = PosRegRatio;
            CurrentNegRatio = NegRegRatio;

            precision = (double)recognitionCounts[3] / (recognitionCounts[3] + recognitionCounts[1]);
            recall = (double)recognitionCounts[3] / (recognitionCounts[3] + recognitionCounts[2]);
            //accuracy = (double)(recognitionCounts[0] + recognitionCounts[3]) / (recognitionCounts[0] + recognitionCounts[1] + recognitionCounts[2] + recognitionCounts[3]);
            fvalue = precision * recall / (precision + recall) * 2;

            precision = (double)recognitionCounts[0] / (recognitionCounts[0] + recognitionCounts[1]);
            recall = (double)recognitionCounts[0] / (recognitionCounts[0] + recognitionCounts[2]);
            accuracy = (double)(recognitionCounts[0] + recognitionCounts[3]) / (recognitionCounts[0] + recognitionCounts[1] + recognitionCounts[2] + recognitionCounts[3]);
            fvalue2 = precision * recall / (precision + recall) * 2;

            //output += "" + PosRegRatio + "\t" + NegRegRatio + "\t";
            //return output;
        }

    }
}
